package com.alone.jpa.query.condition;

import cn.hutool.core.collection.CollectionUtil;
import com.alone.jpa.query.wrapper.ISpecificationWrapper;
import com.alone.jpa.query.condition.impl.SimpleCondition;
import com.alone.jpa.query.condition.impl.SimpleConditionResolver;

import javax.persistence.criteria.*;
import java.util.*;

/**
 * 谓词构建器
 *
 * @author Alone
 */
@SuppressWarnings("all")
public final class PredicateBuilder {

    /**
     * 全局缓存一份
     */
    private static final Map<ConditionType, SimpleConditionResolver> SIMPLE_CONDITION_RESOLVER_MAP;

    static {
        SIMPLE_CONDITION_RESOLVER_MAP = new HashMap<>();
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.EQ, SimpleConditionResolver.EQ_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.GE, SimpleConditionResolver.GE_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.LE, SimpleConditionResolver.LE_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.GT, SimpleConditionResolver.GT_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.LT, SimpleConditionResolver.LT_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.NE, SimpleConditionResolver.NE_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.BETWEEN, SimpleConditionResolver.BETWEEN_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.NOT_BETWEEN, SimpleConditionResolver.NOT_BETWEEN_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.IN, SimpleConditionResolver.IN_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.NOT_IN, SimpleConditionResolver.NOT_IN_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.IS_NOT_NULL, SimpleConditionResolver.IS_NOT_NUL_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.IS_NULL, SimpleConditionResolver.IS_NULL_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.LIKE, SimpleConditionResolver.LIKE_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.NOT_LIKE, SimpleConditionResolver.NOT_LIKE_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.LIKE_RIGHT, SimpleConditionResolver.LIKE_RIGHT_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.NOT_LIKE_RIGHT, SimpleConditionResolver.NOT_LIKE_RIGHT_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.LIKE_LEFT, SimpleConditionResolver.LIKE_LEFT_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.NOT_LIKE_LEFT, SimpleConditionResolver.NOT_LIKE_LEFT_RESOLVER);
        SIMPLE_CONDITION_RESOLVER_MAP.put(ConditionType.HQL, SimpleConditionResolver.HQL_RESOLVER);
    }

    private final List<Condition> conditionList = new LinkedList<>();

    public void addCondition(Condition condition) {
        conditionList.add(condition);
    }

    /**
     * 将Condition通过ConditionResolver转化为Predicate
     */
    public <Type> List<Predicate> build(Root<Type> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        if (CollectionUtil.isEmpty(conditionList)) {
            return null;
        }
        // 目前只用来处理 or方法的 上下节点状态
        Conjunction conjunction = new Conjunction();
        LinkedList<Predicate> predicates = new LinkedList<>();
        for (Condition condition : conditionList) {
            if (condition.equals(SimpleCondition.OR_CONDITION)) {
                conjunction.set();
                continue;
            }
            Predicate predicate = null;
            if (condition instanceof SimpleCondition) {
                predicate = simpleConditionHandle((SimpleCondition) condition, root, query, criteriaBuilder);
            } else {
                // 未知条件类型
                continue;
            }
            // 单个or的处理
            if (conjunction.get()) {
                Predicate pre = predicates.pollLast();
                predicate = criteriaBuilder.or(pre, predicate);
            }
            predicates.add(predicate);
        }
        return predicates;
    }

    private <Type> Predicate handleOr(Root<Type> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, Object value) {
        ISpecificationWrapper<Type, ?, ?> wrapper = (ISpecificationWrapper) Objects.requireNonNull(value);
        return criteriaBuilder.or(wrapper.getPredicates(root, query, criteriaBuilder).toArray(new Predicate[0]));
    }

    private <Type> Predicate handleAnd(Root<Type> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder, Object value) {
        ISpecificationWrapper<Type, ?, ?> wrapper = (ISpecificationWrapper) Objects.requireNonNull(value);
        return criteriaBuilder.and(wrapper.getPredicates(root, query, criteriaBuilder).toArray(new Predicate[0]));
    }

    private <Type> Predicate simpleConditionHandle(SimpleCondition condition, Root<Type> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        Predicate predicate = null;
        if (condition.getConditionType().equals(ConditionType.AND_INNER)) {
            predicate = handleAnd(root, query, criteriaBuilder, condition.getValue());
        } else if (condition.getConditionType().equals(ConditionType.OR_INNER)) {
            predicate = handleOr(root, query, criteriaBuilder, condition.getValue());
        } else {
            Path<?> path = null;
            if (Objects.nonNull(condition.getColumn())) {
                path = root.get(condition.getColumn());
            }
            if (condition.isValueIsColumn()) {
                Path<?> path2 = root.get((String) Objects.requireNonNull(condition.getValue()));
                predicate = SIMPLE_CONDITION_RESOLVER_MAP.get(condition.getConditionType()).resolve(path, path2, criteriaBuilder);
            } else {
                predicate = SIMPLE_CONDITION_RESOLVER_MAP.get(condition.getConditionType()).resolve(path, condition.getValue(), criteriaBuilder);
            }
        }
        return predicate;
    }

    /**
     * 适配 {@link ISpecificationWrapper#or()} {@link ISpecificationWrapper#or(boolean)} )} 方法<br/>
     */
    private static class Conjunction {
        private boolean flag = false;

        public boolean get() {
            if (flag) {
                flag = false;
                return true;
            }
            return false;
        }

        public void set() {
            flag = true;
        }
    }

}
